#include "primitive.h"

#include <QDebug>
#include <algorithm>


bool sort_floats(float a, float b){
    return a < b;
}


#include <algorithm>

Primitive::Primitive()
{
    t[1] = t[0] = 0;
    m_origin = glm::vec4(0,0,0, 1.0);	// Point ends with 1.0
}


Sphere::Sphere(){
    m_radius = 1;
}

bool Sphere::ray_collision(Ray r){

    float a = (r.m_direction.x*r.m_direction.x + r.m_direction.y*r.m_direction.y + r.m_direction.z*r.m_direction.z);
    float b = 2*(r.m_direction.x*(r.m_origin.x - m_origin.x) + (r.m_direction.y*(r.m_origin.y - m_origin.y)) + (r.m_direction.z*(r.m_origin.z - m_origin.z)));
    float c = -m_radius*m_radius + (r.m_origin.x - m_origin.x)*(r.m_origin.x - m_origin.x) + (r.m_origin.y - m_origin.y)*(r.m_origin.y - m_origin.y) + (r.m_origin.z - m_origin.z)*(r.m_origin.z - m_origin.z);
    float delta = b*b - 4*a*c;

    if ( delta >= 0) {
        float sqrDelta = sqrt(delta);
        t[0] = (-b - sqrDelta)/(2*a);
        t[1] = (-b + sqrDelta)/(2*a);
        normal[0] = r.m_origin + t[0]*r.m_direction - m_origin;
        normal[1] = r.m_origin + t[1]*r.m_direction - m_origin;
        return true;
    } else {
        return false;
    }
}

Cylinder::Cylinder()
{
    m_origin = glm::vec4(0.0,0.0,0.0,1.0);
    m_radius = 0.5;
    m_height = 2.0;
    float half_height = m_height/2;
    m_bottom_point = glm::vec4(m_origin.x,m_origin.y - half_height,m_origin.z,1.0);
    m_top_point = glm::vec4(m_origin.x,m_origin.y + half_height,m_origin.z,1.0);
}

bool Cylinder::ray_collision(Ray r)
{
    return collide(r);
}

bool Cylinder::collide(Ray r)
{
    std::vector<float> t_values;
    glm::vec4 collision_point = glm::vec4(0.0,0.0,0.0,1.0);
    float x2;
    float z2;
    float r2 = m_radius*m_radius;
    float t_top;
    float t_bottom;

    t_top = (m_top_point.y - r.m_origin.y)/r.m_direction.y;
    collision_point = r.m_origin + (t_top*r.m_direction);
    x2 = (collision_point.x -  m_origin.x)*(collision_point.x -  m_origin.x);
    z2 = (collision_point.z -  m_origin.z)*(collision_point.z -  m_origin.z);
    if((x2 + z2 <= r2)){
        t_values.push_back(t_top);
    }


    t_bottom = (m_bottom_point.y - r.m_origin.y)/r.m_direction.y;
    collision_point = r.m_origin + (t_bottom*r.m_direction);
    x2 = (collision_point.x -  m_origin.x)*(collision_point.x -  m_origin.x);
    z2 = (collision_point.z -  m_origin.z)*(collision_point.z -  m_origin.z);
    if((x2 + z2 <= r2)){
        t_values.push_back(t_bottom);
    }

    float a = (r.m_direction.x*r.m_direction.x + r.m_direction.z*r.m_direction.z);
    float b = 2*(r.m_direction.x*(r.m_origin.x - m_origin.x) + (r.m_direction.z*(r.m_origin.z - m_origin.z)));
    float c = -m_radius*m_radius + (r.m_origin.x - m_origin.x)*(r.m_origin.x - m_origin.x) + (r.m_origin.z - m_origin.z)*(r.m_origin.z - m_origin.z);
    float delta = b*b - 4*a*c;
    float delta_sqrt;
    float t_side1;
    float t_side2;


    if(delta >= 0){
        delta_sqrt = sqrt(delta);
        t_side1 = (-b + delta_sqrt)/(2*a);
        t_side2 = (-b - delta_sqrt)/(2*a);
        if(t_side1 > 0){
            collision_point = r.m_origin + (t_side1*r.m_direction);
            if(collision_point.y >= m_bottom_point.y && collision_point.y <= m_top_point.y){
                t_values.push_back(t_side1);
            }
        }
        if(t_side2 > 0){
            collision_point = r.m_origin + (t_side2*r.m_direction);
            if(collision_point.y >= m_bottom_point.y && collision_point.y <= m_top_point.y){
                t_values.push_back(t_side2);
            }
        }
    }

    if(t_values.size() == 0){
        return false;
    }

    std::sort(t_values.begin(), t_values.end(), &sort_floats);
    t[1] = t_values[1];
    normal[1] = glm::normalize(collision_point - m_origin);

    t[0] = t_values[0];
    normal[0] = glm::normalize(collision_point - m_origin);
    return true;
}

Cube::Cube()
{
    m_origin = glm::vec4(0.0,0.0,0.0,1.0);
    m_width = 1.0;
    m_height = 1.0;
    m_depth = 1.0;
    float half_width = m_width/2;
    float half_height = m_height/2;
    float half_depth = m_depth/2;
    m_top_point = glm::vec4(m_origin.x,m_origin.y + half_height,m_origin.z,1.0);
    m_bottom_point = glm::vec4(m_origin.x,m_origin.y - half_height, m_origin.z,1.0);
    m_left_point = glm::vec4(m_origin.x - half_width, m_origin.y, m_origin.z,1.0);
    m_right_point = glm::vec4(m_origin.x + half_width, m_origin.y, m_origin.z,1.0);
    m_front_point = glm::vec4(m_origin.x,m_origin.y,m_origin.z + half_depth,1.0);
    m_back_point = glm::vec4(m_origin.x,m_origin.y,m_origin.z - half_depth,1.0);
}

bool Cube::ray_collision(Ray r){
    return collide(r);
}

bool Cube::collide(Ray r){
    std::vector<float> t_values;
    glm::vec4 collision_point = glm::vec4(0.0,0.0,0.0,1.0);
    float num;
    float den;
    float t_top, t_bottom, t_left, t_right, t_front, t_back;
    glm::vec4 current_normal = glm::vec4(0.0,0.0,0.0,0.0);

    //front
    current_normal = glm::normalize(m_front_point - m_origin);
    num = glm::dot(current_normal,(m_front_point - r.m_origin));
    den = glm::dot(current_normal, r.m_direction);
    if(den != 0){
        t_front = num/den;
        collision_point = r.m_origin + (t_front*r.m_direction);
        if(check_point_collision(collision_point)){
            t_values.push_back(t_front);
        }
    }

    //back
    current_normal = glm::normalize(m_back_point - m_origin);
    num = glm::dot(current_normal,(m_back_point - r.m_origin));
    den = glm::dot(current_normal, r.m_direction);
    if(den != 0){
        t_back = num/den;
        collision_point = r.m_origin + (t_back*r.m_direction);
        if(check_point_collision(collision_point)){
            t_values.push_back(t_back);
        }
    }

    //top
    current_normal = glm::normalize(m_top_point - m_origin);
    num = glm::dot(current_normal,(m_top_point - r.m_origin));
    den = glm::dot(current_normal, r.m_direction);
    if(den != 0){
        t_top = num/den;
        collision_point = r.m_origin + (t_top*r.m_direction);
        if(check_point_collision(collision_point)){
            t_values.push_back(t_top);
        }
    }

    //bottom
    current_normal = glm::normalize(m_bottom_point - m_origin);
    num = glm::dot(current_normal,(m_bottom_point - r.m_origin));
    den = glm::dot(current_normal, r.m_direction);
    if(den != 0){
        t_bottom = num/den;
        collision_point = r.m_origin + (t_bottom*r.m_direction);
        if(check_point_collision(collision_point)){
            t_values.push_back(t_bottom);
        }
    }


    //left
    current_normal = glm::normalize(m_left_point - m_origin);
    num = glm::dot(current_normal,(m_left_point - r.m_origin));
    den = glm::dot(current_normal, r.m_direction);
    if(den != 0){
        t_left = num/den;
        collision_point = r.m_origin + (t_left*r.m_direction);
        if(check_point_collision(collision_point)){
            t_values.push_back(t_left);
        }
    }

    //right
    current_normal = glm::normalize(m_right_point - m_origin);
    num = glm::dot(current_normal,(m_right_point - r.m_origin));
    den = glm::dot(current_normal, r.m_direction);
    if(den != 0){
        t_right = num/den;
        collision_point = r.m_origin + (t_right*r.m_direction);
        if(check_point_collision(collision_point)){
            t_values.push_back(t_right);
        }
    }

    if(t_values.size() > 0){
        std::sort(t_values.begin(),t_values.end(),&sort_floats);
        t[1] = t_values[1];
        //Remove this later
        m_collision_point = r.m_origin + (t[1]*r.m_direction);
        m_collision_normal = glm::normalize(m_collision_point - m_origin);
        //End remove this later
        normal[1] = glm::normalize(m_collision_point - m_origin);

        t[0] = t_values[0];
        //Remove this later
        m_collision_point = r.m_origin + (t[0]*r.m_direction);
        m_collision_normal = glm::normalize(m_collision_point - m_origin);
        //End remove this later
        normal[0] = glm::normalize(m_collision_point - m_origin);

        return true;
    }
    return false;

}

bool Cube::check_point_collision(glm::vec4 point){
    float error = 0.000001;
    float min_x = m_origin.x - (m_width/2) - error;
    float max_x = m_origin.x + (m_width/2) + error;
    float min_y = m_origin.y - (m_height/2) - error;
    float max_y = m_origin.y + (m_height/2) + error;
    float min_z = m_origin.z - (m_depth/2) - error;
    float max_z = m_origin.z + (m_depth/2) + error;

    if((min_x <= point.x) && (point.x <= max_x)){
        if((min_y <= point.y) && (point.y <= max_y)){
            if((min_z <= point.z) && (point.z <= max_z)){
                return true;
            }
            return false;
        }
        return false;
    }
    return false;
}


Cone::Cone()
{
    m_origin = glm::vec4(0.0,0.0,0.0,1.0);
    m_base_radius = 1.0;
    m_height = 1.0;
}

bool Cone::ray_collision(Ray r)
{
    float a = r.m_direction.x*r.m_direction.x - r.m_direction.y*r.m_direction.y + r.m_direction.z*r.m_direction.z;
    float b = 2*(r.m_origin.x*r.m_direction.x - r.m_origin.y*r.m_direction.y + r.m_origin.z*r.m_direction.z);
    float c = r.m_origin.x*r.m_origin.x - r.m_origin.y*r.m_origin.y + r.m_origin.z*r.m_origin.z;

    float delta = b*b - 4*a*c;
    if (delta >= 0){

        float t_aux;

        bool intersect_base = false;

        // Verifica se a direção do raio não é perpendicular a normal do plano (0, -1, 0)
        // ou seja, se o produto escalar dos dois vetores é diferente de zero
        if (r.m_direction.y != 0) {


            // Calcula o valor de t sabendo que a coordenada Y do ponto em que o raio intersecta a base do cone
            // (plano XZ) é -1
            t_aux = (-1-r.m_origin.y)/r.m_direction.y;


            // Verifica se o ponto está dentro dos limites da base do cone
            if ((r.m_direction.x*r.m_direction.x + r.m_direction.z*r.m_direction.z) <= 1) {
                t[0] = t_aux;
                normal[0].x = 0;
                normal[0].y = -1;
                normal[0].z = 0;

                intersect_base = true;
            }
        }

        float delta_root = sqrt(delta);
        t_aux = (-b - delta_root)/2*a;

        // Armazena a coordenada Y do ponto em que o raio intersecta o cone
        float y_intersect = r.m_origin.y + t_aux*r.m_direction.y;

        // Ponto em que o raio interesecta o cone
        glm::vec4 m_vertex;
        float dist, y_ref;

        glm::vec4 ref_vertex;
        glm::vec4 intersec_normal;

        // Verifica se a coordenada Y do ponto está dentro dos limites que interessam na região cônica
        if (y_intersect <= 0 && y_intersect >= -1) {

            // Calcula a normal no ponto de intersecção
            {
                m_vertex = r.m_origin + t_aux*r.m_direction;

                dist = sqrt((m_vertex.x*m_vertex.x) + (m_vertex.y)*(m_vertex.y) + (m_vertex.z*m_vertex.z));

                y_ref = dist*sqrt(2);

                ref_vertex.x = 0;
                ref_vertex.y = -y_ref;
                ref_vertex.z = 0;

                intersec_normal = m_vertex - ref_vertex;
            }

            // Se o raio intersecta a base, então os dois pontos de intersecção foram encontrados
            if (intersect_base) {
                t[1] = t_aux;
                normal[1] = intersec_normal;
                return true;
            } else {
                t[0] = t_aux;
                normal[0] = intersec_normal;

                // Se o delta é igual a zero, só existe um ponto de intersecção
                if (delta == 0){
                    t[1] = t[0];
                    normal[1] = normal[0];
                    return true;
                }
            }
        }

        t_aux = (-b + delta_root)/2*a;
        y_intersect = r.m_origin.y + t_aux*r.m_direction.y;

        // Verifica se a coordenada Y do segundo ponto de intersecção
        // está dentro dos limites que interessam na região cônica
        if (y_intersect <= 0 && y_intersect >= -1) {

            m_vertex = r.m_origin + t_aux*r.m_direction;

            // Calcula a normal no ponto de intersecção
            {
                dist = sqrt((m_vertex.x*m_vertex.x) + (m_vertex.y)*(m_vertex.y) + (m_vertex.z*m_vertex.z));

                y_ref = dist*sqrt(2);

                ref_vertex.x = 0;
                ref_vertex.y = -y_ref;
                ref_vertex.z = 0;

                intersec_normal = m_vertex - ref_vertex;
            }

            // Como algum dos pontos de intersecção (base ou casca) já foi calculado
            // basta definirmos o segundo ponto
            t[1] = t_aux;
            normal[1] = intersec_normal;

            return true;
        }
    }
    return false;
}
